package vsvm.math.statistics;

import java.util.Iterator;

import org.apache.commons.math.MathException;
import org.apache.commons.math.distribution.NormalDistributionImpl;

import vsvm.classifier.AbstractTestSet;
import vsvm.classifier.TestCase;
import vsvm.exceptions.IncompatableTestSetsException;
import vsvm.math.statistics.distributions.NormalDistribution;

/**
 *  -------------------------------------------------------------
 *  |               | Classifier2_t | classifier2_f |           | 
 *  -------------------------------------------------------------
 *  | Classifier1_t |     tt        |      tf       |  total1_t |
 *  | Classifier1_f |     ft        |      ff       |  total1_f |
 *  -------------------------------------------------------------
 *  |				|    total2_t   |     total2_f  |  total    |
 *  -------------------------------------------------------------
 *
 *  Corrected estimate: z = (|tf - ft| - 1)/(tf + ft)
 *  Z ~ N(0, 1)           
 */

public class McNemarTest {
	
	int tt, tf, ft, ff;
	AbstractTestSet t1;
	AbstractTestSet t2;
	
	/**
	 * Significance level
	 */
	private double alpha = 0.05;
	
	public McNemarTest(AbstractTestSet t1, AbstractTestSet t2)
	throws IncompatableTestSetsException {
		
		tt = tf = ft = ff = 0;
		this.t1 = t1; this.t2 = t2;
		performTest(t1, t2);
	}
	
	public int[][] getCountTable() {
		return new int[][]{{tt, tf, tt + tf}, {ft, ff, ft + ff}, {tt + ft, tf + ff, tt + tf + ft + ff}};
	}
	
	public double [][] getFrequencyTable() {
		double total = tt + tf + ft + ff;
		return new double[][] {{tt/total, tf/total, (tt + tf)/total}, 
							   {ft/total, ff/total, (ft + ff)/total}, 
							   {(tt + ft)/total, (tf + ff)/total, (tt + tf + ft + ff)/total}};
	}
	
	private void performTest(AbstractTestSet t1, AbstractTestSet t2)
	throws IncompatableTestSetsException
	{
		if (t1.getTestCasesCount() != t2.getTestCasesCount()) {
			throw new IncompatableTestSetsException("Not equal test cases counts");
		}		
		
		Iterator <TestCase> it1 = t1.getTestCases();
		Iterator <TestCase> it2 = t2.getTestCases();
		
		while (it1.hasNext()) {
			TestCase cur1 = it1.next();
			TestCase cur2 = it2.next();
			if (Math.abs(cur1.getActualClass() - cur1.getPredictedClass()) < 1e-8)  {
				if (Math.abs(cur2.getActualClass() - cur2.getPredictedClass()) < 1e-8)
					tt++;
				else 
					tf++;				
			} else
				if (Math.abs(cur2.getActualClass() - cur2.getPredictedClass()) < 1e-8)
					ft++;
				else
					ff++;
		}		
	}	
	
	
	/**
	 *  z = (|tf - ft| - 1)/sqrt(tf + ft)
	 */
	
	//TODO exact fisher test if tf + ft < 10
	public double getCorrectedEstimate() {
		return (Math.abs(tf - ft) - 1)/Math.sqrt(tf + ft);
	}
	
	public double getSignificanceLevel() {
		return this.alpha;		
	}
	
	public void setSignificanceLevel(double alpha) {
		this.alpha = alpha;
	}
	
	/**
	 *  P value for two sided test:
	 *  p = 2*P(Z >= |Estimate|)
	 *  
	 *  Default significance level - 0.05
	 */
	
	public double getPValue() throws MathException {
		NormalDistribution nd = new NormalDistribution();
		return 2*(1 - nd.cumulativeProbability(Math.abs(this.getCorrectedEstimate())));		
	}
	
	public AbstractTestSet[] getTestSets() {
		return new AbstractTestSet[]{t1, t2};
	}
}
